// Copyright (c) 2010, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// ---
// Author: Jeffrey Junfeng Pan (panjunfeng@gmail.com)

// access transactions that stored in a file.

#ifndef FP_GROWTH_TRANSACTIONS_FILE_IMPL_H_
#define FP_GROWTH_TRANSACTIONS_FILE_IMPL_H_

#include <fstream>
#include <string>
#include <string.h>
#include <vector>
#include <iostream>

#include "common/scoped_ptr.h"
#include "common/strutil.h"
#include "transactions.h"

#define MAX_LINE_SIZE 2048

using namespace std;

namespace frequent_pattern_mining {

// ------------------------------------------------------------
//                      Declaration
// ------------------------------------------------------------

template <typename T>
class TransactionsFileImpl : public Transactions<T> {
 public:

  // Use external file to initialize the transaction data source.
  // transaction_file is the file name. each transaction is a line
  // in the file. item_delimiter is the delimiter for separating
  // items in a transaction. Typical delimiter can be whitespace
  // or comma, etc.
  explicit TransactionsFileImpl(string transactions_file,
                                string item_delimiter);

  // Destructor.
  ~TransactionsFileImpl();

  // Return the size of transactions. size() is usually called
  // after a complete file scan. Otherwise, it needs extra
  // effort for counting transactions.
  int size();

  // If there is no transaction at all.
  bool empty();

  // Return the trancation given the index.
  // This operator is efficient if data is queried sequentially.
  // For example, index = 0, 1, 2, 3...
  vector<T>& operator[](int index);

  // Return the frequency given the index.
  // This operator is efficient if data is queried sequentially.
  // For example, index = 0, 1, 2, 3...
  int operator()(int index);

  // Whether or not the transaction exists given the index.
  // This operator is efficient if data is queried sequentially.
  // For example, index = 0, 1, 2, 3...
  bool is_valid(int index);

 private:

  // read the transaction and cache it in transaction_
  bool read_transaction(int index);

  // the index of the cached transaction.
  int cursor_;

  // the size of the while transactions. size_ = -1 if it is unknown.
  // For example, if we have not yet finished a complete scan of data.
  int size_;

  // delimiter for separating items in a transaction (as a string).
  string item_delimiter_;

  // the cached transaction.
  vector<T> transaction_;

  // access the transaction file.
  scoped_ptr<ifstream> input_buffer_;

  DISALLOW_EVIL_CONSTRUCTORS(TransactionsFileImpl);
};

// ------------------------------------------------------------
//                      Definition
// ------------------------------------------------------------

// Constructor.
// Use external file to initialize the transaction data source.
// transaction_file is the file name. each transaction is a line
// in the file. item_delimiter is the delimiter for separating
// items in a transaction. Typical delimiter can be whitespace
// or comma, etc.
template <typename T>
TransactionsFileImpl<T>::TransactionsFileImpl(string transactions_file,
                                              string item_delimiter)
  : cursor_(-1),
    size_(-1),
    item_delimiter_(item_delimiter)  {
  input_buffer_.reset(new ifstream);
  input_buffer_.get()->open(transactions_file.c_str());
}

// Destructor.
template <typename T>
TransactionsFileImpl<T>::~TransactionsFileImpl() {
  input_buffer_.get()->close();
}

// Return the size of transactions. size() is usually called
// after a complete file scan. Otherwise, it needs extra
// effort for counting transactions.
template <typename T>
int TransactionsFileImpl<T>::size() {
  if (size_ < 0) {
    LOG(WARNING) << "size() is called before a complete scan of transactions. "
                    "performance may suffer.";
    int index = cursor_;
    string line;
    char buffer[MAX_LINE_SIZE];
    streampos pos = input_buffer_->tellg();  // save current position.
    while (input_buffer_->getline(buffer, sizeof(buffer))) {
        cout << buffer << endl;
        ++index;
    }
    input_buffer_->seekg(pos);  // go back to the saved position.
    size_ = index + 1;
  }
  return size_;
}

// If there is no transaction at all.
template <typename T>
bool TransactionsFileImpl<T>::empty() {
  return !is_valid(0);
}

// read the transaction and cache it in transaction_
template <typename T>
bool TransactionsFileImpl<T>::read_transaction(int index) {
  // if the transaction is already cached.
  if (cursor_ == index) {
    return true;
  }

  // if the transaction is not sequentially read, reset file pointer.
  if (cursor_ > index) {
    LOG_IF(WARNING, index > 0) << "transactions are not read sequentially."
                           "search from beginning. performance may suffer.";
    input_buffer_->clear();
    input_buffer_->seekg(0, fstream::beg);
    cursor_ = -1;
  }

  // search for the expected transaction given the index.
  string line;
  char buffer[MAX_LINE_SIZE];
  while (cursor_ < index) {
    if (input_buffer_->getline(buffer, sizeof(buffer))) {
      ++cursor_;
    } else {
      size_ = cursor_ + 1;
      break;
    }
  }

  // if the expected transaction is found.
  if (cursor_ == index) {
    transaction_.clear();
    char* pch = strtok(buffer, item_delimiter_.c_str());
    while (pch != NULL) {
      T item;
      ItemFromString(pch, &item);
      transaction_.push_back(item);
      pch = strtok(NULL, item_delimiter_.c_str());
    }
    return true;
  } else {
    return false;
  }
}

// Return the trancation given the index.
// This operator is efficient if data is queried sequentially.
// For example, index = 0, 1, 2, 3...
template <typename T>
vector<T>& TransactionsFileImpl<T>::operator[](int index) {
  CHECK_GE(index, 0) << ": index must be >= 0";
  CHECK_EQ(true, size_ < 0 || index < size_)
    << ": index must be < size of transaction";
  CHECK_EQ(true, read_transaction(index))
    << ": error in reading transaction[" << index << "]";
  return transaction_;
}

// Return the frequency given the index.
// This operator is efficient if data is queried sequentially.
// For example, index = 0, 1, 2, 3...
template <typename T>
int TransactionsFileImpl<T>::operator()(int index) {
  CHECK_EQ(true, is_valid(index)) << ": index is out of range";
  return 1;
}

// Whether or not the transaction exists given the index.
// This operator is efficient if data is queried sequentially.
// For example, index = 0, 1, 2, 3...
template <typename T>
bool TransactionsFileImpl<T>::is_valid(int index) {
  if (index >= 0 && (index <= cursor_ || index < size_)) {
    return true;
  } else if (index < 0 || (size_ >= 0 && index >= size_)) {
    return false;
  } else if (size_ == -1 && index > cursor_) {
    return read_transaction(index);
  } else {
    LOG(ERROR) << ": impossible case. fix me!";
    return false;
  }
}
}  // namespace frequent_pattern_mining

#endif  // FP_GROWTH_TRANSACTIONS_FILE_IMPL_H_
